#pragma once
#ifndef MARKERTABLE_HPP
#define MARKERTABLE_HPP

#include <stdio.h>
#include <opencv2/opencv.hpp>
#include <opencv2/aruco.hpp>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <string>
#include <vector>
#include "BitBuffer.hpp"
#include "QrCode.hpp"
#include <opencv/cv.h>
#include <opencv/highgui.h>

using std::uint8_t;
using qrcodegen::QrCode;
using qrcodegen::QrSegment;

class MarkerTable
{
public:
	static cv::Mat qrmarker(int markerId, int markerSize = 300, int blankSize = 50)
	{
		char qrnum[5];
		sprintf_s(qrnum, "%04X", markerId);
		QrCode qr = QrCode::encodeText(qrnum, QrCode::Ecc::HIGH);
		cv::Mat qrImg;
		////////////////////////////////////
		cv::Scalar s(255);
		cv::Mat qrframe(21, 21, CV_8UC1, s);
		char buf[32];
		//sprintf_s(buf, "%d.png", qr.getSize());
		for (int y = 0; y < qr.getSize(); y++) {
			uchar* data = qrframe.ptr<uchar>(y);
			for (int x = 0; x < qr.getSize(); x++) {
				if (qr.getModule(x, y) == 1) {
					data[x] = 0;
				}
			}
		}
		resize(qrframe, qrImg, cv::Size(markerSize, markerSize), 0, 0, cv::INTER_NEAREST);
		/////////////////////////////////////
		cv::Mat canvas(markerSize + 2 * blankSize, markerSize + 2 * blankSize, qrImg.type(), cv::Scalar(255));
		qrImg.copyTo(canvas(cv::Rect(blankSize, blankSize, markerSize, markerSize)));
		return canvas;
	}

	static cv::Mat qrmarkerWithId(int markerId, int markerSize = 300, int blankSize = 50, int gap = 4)
	{
		cv::Mat canvas = qrmarker(markerId, markerSize, blankSize);
		char buf[8];
		//sprintf_s(buf, "%d", markerId);
		sprintf_s(buf, "00%02X", markerId);
		cv::putText(canvas, buf, cv::Point(gap, canvas.rows - gap), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0), 2);
		return canvas;
	}

	static cv::Mat qrmarkerWithIdTriangle(int markerId, int markerSize = 300, int blankSize = 50, int triangleSize = 20, int gap = 4)
	{
		cv::Mat canvas = qrmarkerWithId(markerId, markerSize, blankSize, gap);

		std::vector< std::vector<cv::Point> > triangleVertex(4, std::vector<cv::Point>(3, cv::Point()));

		triangleVertex[0][0] = cv::Point(canvas.cols / 2, 1);
		triangleVertex[0][1] = cv::Point(canvas.cols / 2 - triangleSize, triangleSize + 1);
		triangleVertex[0][2] = cv::Point(canvas.cols / 2 + triangleSize, triangleSize + 1);

		triangleVertex[1][0] = cv::Point(canvas.cols / 2, canvas.rows - 2);
		triangleVertex[1][1] = cv::Point(canvas.cols / 2 - triangleSize, canvas.rows - 2 - triangleSize);
		triangleVertex[1][2] = cv::Point(canvas.cols / 2 + triangleSize, canvas.rows - 2 - triangleSize);

		triangleVertex[2][0] = cv::Point(1, canvas.rows / 2);
		triangleVertex[2][1] = cv::Point(triangleSize + 1, canvas.rows / 2 - triangleSize);
		triangleVertex[2][2] = cv::Point(triangleSize + 1, canvas.rows / 2 + triangleSize);

		triangleVertex[3][0] = cv::Point(canvas.cols - 2, canvas.rows / 2);
		triangleVertex[3][1] = cv::Point(canvas.cols - 2 - triangleSize, canvas.rows / 2 - triangleSize);
		triangleVertex[3][2] = cv::Point(canvas.cols - 2 - triangleSize, canvas.rows / 2 + triangleSize);


		cv::fillPoly(canvas, triangleVertex, cv::Scalar::all(0));

		return canvas;
	}

	static cv::Mat qrmarkerWithIdTriangleOutline(int markerId, int markerSize = 300, int blankSize = 105, int triangleSize = 30, int frameThick = 1, int gap = 20)
	{
		cv::Mat canvas = qrmarkerWithIdTriangle(markerId, markerSize, blankSize, triangleSize, gap);

		cv::Mat frame(canvas.size(), canvas.type(), cv::Scalar::all(255));

		frame(cv::Rect(0, 0, canvas.cols, canvas.rows)).setTo(150);
		frame(cv::Rect(frameThick, frameThick, canvas.cols - 2 * frameThick, canvas.rows - 2 * frameThick)).setTo(255);

		cv::bitwise_and(canvas, frame, canvas);

		return canvas;
	}

	static cv::Mat qrtable(cv::Size tablesz, int firstId, int type)
	{
		cv::Mat canvas;

		for (int i = 0; i<tablesz.height; i++)
		{
			for (int j = 0; j<tablesz.width; j++)
			{
				cv::Mat markerImg;
				switch (type)
				{
				case 0:
					markerImg = qrmarker(i*tablesz.width + j + firstId);
					break;
				case 1:
					markerImg = qrmarkerWithId(i*tablesz.width + j + firstId);
					break;
				case 3:
					markerImg = qrmarkerWithIdTriangle(i*tablesz.width + j + firstId);
					break;
				case 5:
					markerImg = qrmarkerWithIdTriangleOutline(i*tablesz.width + j + firstId);
					break;
				}
				if (canvas.empty())
				{
					canvas.create(markerImg.rows*tablesz.height, markerImg.cols*tablesz.width, markerImg.type());
				}
				markerImg.copyTo(canvas(cv::Rect(j*markerImg.cols, i*markerImg.rows, markerImg.cols, markerImg.rows)));
			}
		}
		return canvas;
	}


	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	static cv::Mat marker(cv::Ptr<cv::aruco::Dictionary> &dictionary, int markerId, int borderBits = 1, int markerSize = 300, int blankSize = 50)
	{
		cv::Mat markerImg;
		cv::aruco::drawMarker(dictionary, markerId, markerSize, markerImg, borderBits);
		cv::Mat canvas(markerSize + 2 * blankSize, markerSize + 2 * blankSize, markerImg.type(), cv::Scalar(255));
		markerImg.copyTo(canvas(cv::Rect(blankSize, blankSize, markerSize, markerSize)));
		return canvas;
	}

	static cv::Mat markerWithId(cv::Ptr<cv::aruco::Dictionary> &dictionary, int markerId, int borderBits = 1, int markerSize = 300, int blankSize = 50, int gap = 4)
	{
		cv::Mat canvas = marker(dictionary, markerId, borderBits, markerSize, blankSize);
		char buf[8];
		//sprintf_s(buf, "%d", markerId);
		sprintf_s(buf, "%d", 1000 - 80 + markerId);
		cv::putText(canvas, buf, cv::Point(gap, canvas.rows - gap), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0), 2);
		return canvas;
	}

	static cv::Mat markerWithIdTriangle(cv::Ptr<cv::aruco::Dictionary> &dictionary, int markerId, int borderBits = 1, int markerSize = 300, int blankSize = 50, int triangleSize = 20, int gap = 4)
	{
		cv::Mat canvas = markerWithId(dictionary, markerId, borderBits, markerSize, blankSize, gap);

		std::vector< std::vector<cv::Point> > triangleVertex(4, std::vector<cv::Point>(3, cv::Point()));

		triangleVertex[0][0] = cv::Point(canvas.cols / 2, 0);
		triangleVertex[0][1] = cv::Point(canvas.cols / 2 - triangleSize, triangleSize);
		triangleVertex[0][2] = cv::Point(canvas.cols / 2 + triangleSize, triangleSize);

		triangleVertex[1][0] = cv::Point(canvas.cols / 2, canvas.rows - 1);
		triangleVertex[1][1] = cv::Point(canvas.cols / 2 - triangleSize, canvas.rows - 1 - triangleSize);
		triangleVertex[1][2] = cv::Point(canvas.cols / 2 + triangleSize, canvas.rows - 1 - triangleSize);

		triangleVertex[2][0] = cv::Point(0, canvas.rows / 2);
		triangleVertex[2][1] = cv::Point(triangleSize, canvas.rows / 2 - triangleSize);
		triangleVertex[2][2] = cv::Point(triangleSize, canvas.rows / 2 + triangleSize);

		triangleVertex[3][0] = cv::Point(canvas.cols - 1, canvas.rows / 2);
		triangleVertex[3][1] = cv::Point(canvas.cols - 1 - triangleSize, canvas.rows / 2 - triangleSize);
		triangleVertex[3][2] = cv::Point(canvas.cols - 1 - triangleSize, canvas.rows / 2 + triangleSize);


		cv::fillPoly(canvas, triangleVertex, cv::Scalar::all(0));

		return canvas;
	}


	static cv::Mat markerWithIdTriangleFrame(cv::Ptr<cv::aruco::Dictionary> &dictionary, int markerId, int borderBits = 1, int markerSize = 300, int blankSize = 100, int triangleSize = 20, int frameThick = 20, int gap = 4)
	{
		cv::Mat canvas = markerWithIdTriangle(dictionary, markerId, borderBits, markerSize, blankSize, triangleSize, gap);

		cv::Mat frame(canvas.size(), canvas.type(), cv::Scalar::all(255));

		frame(cv::Rect((blankSize - frameThick) / 2, (blankSize - frameThick) / 2, markerSize + blankSize + frameThick, markerSize + blankSize + frameThick)).setTo(0);
		frame(cv::Rect((blankSize + frameThick) / 2, (blankSize + frameThick) / 2, markerSize + blankSize - frameThick, markerSize + blankSize - frameThick)).setTo(255);

		cv::bitwise_and(canvas, frame, canvas);

		return canvas;
	}

	//case 5
	static cv::Mat markerWithIdTriangleOutline(cv::Ptr<cv::aruco::Dictionary> &dictionary, int markerId, int borderBits = 1, int markerSize = 300, int blankSize = 105, int triangleSize = 30, int frameThick = 1, int gap = 20)
	{
		cv::Mat canvas = markerWithIdTriangle2(dictionary, markerId, borderBits, markerSize, blankSize, triangleSize, gap);

		cv::Mat frame(canvas.size(), canvas.type(), cv::Scalar::all(255));

		frame(cv::Rect(0, 0, canvas.cols, canvas.rows)).setTo(150);
		frame(cv::Rect(frameThick, frameThick, canvas.cols - 2 * frameThick, canvas.rows - 2 * frameThick)).setTo(255);

		cv::bitwise_and(canvas, frame, canvas);

		return canvas;

	}

	//triangle2
	static cv::Mat markerWithIdTriangle2(cv::Ptr<cv::aruco::Dictionary> &dictionary, int markerId, int borderBits = 1, int markerSize = 300, int blankSize = 50, int triangleSize = 20, int gap = 4)
	{
		cv::Mat canvas = markerWithId(dictionary, markerId, borderBits, markerSize, blankSize, gap);

		std::vector< std::vector<cv::Point> > triangleVertex(4, std::vector<cv::Point>(3, cv::Point()));

		triangleVertex[0][0] = cv::Point(canvas.cols / 2, 1);
		triangleVertex[0][1] = cv::Point(canvas.cols / 2 - triangleSize, triangleSize + 1);
		triangleVertex[0][2] = cv::Point(canvas.cols / 2 + triangleSize, triangleSize + 1);

		triangleVertex[1][0] = cv::Point(canvas.cols / 2, canvas.rows - 2);
		triangleVertex[1][1] = cv::Point(canvas.cols / 2 - triangleSize, canvas.rows - 2 - triangleSize);
		triangleVertex[1][2] = cv::Point(canvas.cols / 2 + triangleSize, canvas.rows - 2 - triangleSize);

		triangleVertex[2][0] = cv::Point(1, canvas.rows / 2);
		triangleVertex[2][1] = cv::Point(triangleSize + 1, canvas.rows / 2 - triangleSize);
		triangleVertex[2][2] = cv::Point(triangleSize + 1, canvas.rows / 2 + triangleSize);

		triangleVertex[3][0] = cv::Point(canvas.cols - 2, canvas.rows / 2);
		triangleVertex[3][1] = cv::Point(canvas.cols - 2 - triangleSize, canvas.rows / 2 - triangleSize);
		triangleVertex[3][2] = cv::Point(canvas.cols - 2 - triangleSize, canvas.rows / 2 + triangleSize);


		cv::fillPoly(canvas, triangleVertex, cv::Scalar::all(0));

		return canvas;
	}




	static cv::Mat markerWithIdLine(cv::Ptr<cv::aruco::Dictionary> &dictionary, int markerId, int borderBits = 1, int markerSize = 300, int blankSize = 50, int gap = 4, int lineLength = 400)
	{
		cv::Mat marker = markerWithId(dictionary, markerId, borderBits, markerSize, blankSize, gap);
		cv::Mat lineMat(lineLength, marker.cols, marker.type(), cv::Scalar::all(255));
		lineMat.col(blankSize).setTo(0);
		cv::Mat ml[3] = { lineMat,marker,lineMat };
		cv::Mat canvas;
		cv::vconcat(ml, 3, canvas);
		return canvas;
	}

	



	static cv::Mat artable(cv::Ptr<cv::aruco::Dictionary> &dictionary, cv::Size tablesz, int firstId, int type)
	{
		cv::Mat canvas;

		for (int i = 0; i<tablesz.height; i++)
		{
			for (int j = 0; j<tablesz.width; j++)
			{
				cv::Mat markerImg;
				switch (type)
				{
				case 0:
					markerImg = marker(dictionary, i*tablesz.width + j + firstId);
					break;
				case 1:
					markerImg = markerWithId(dictionary, i*tablesz.width + j + firstId);
					break;
				case 2:
					markerImg = markerWithIdLine(dictionary, i*tablesz.width + j + firstId);
					break;
				case 3:
					markerImg = markerWithIdTriangle(dictionary, i*tablesz.width + j + firstId);
					break;
				case 4:
					markerImg = markerWithIdTriangleFrame(dictionary, i*tablesz.width + j + firstId);
					break;
				case 5:
					markerImg = markerWithIdTriangleOutline(dictionary, i*tablesz.width + j + firstId);
					break;
				default:
					markerImg = marker(dictionary, i*tablesz.width + j + firstId);
					break;
				}
				if (canvas.empty())
				{
					canvas.create(markerImg.rows*tablesz.height, markerImg.cols*tablesz.width, markerImg.type());
				}
				markerImg.copyTo(canvas(cv::Rect(j*markerImg.cols, i*markerImg.rows, markerImg.cols, markerImg.rows)));
			}
		}
		return canvas;
	}

	static cv::Mat artable(cv::Size tableSize = cv::Size(5, 7), int firstId = 0, int dictionaryId = 11, int type = 1)
	{
		cv::Ptr<cv::aruco::Dictionary> dictionary1 =
			cv::aruco::getPredefinedDictionary(cv::aruco::PREDEFINED_DICTIONARY_NAME(dictionaryId));
		return artable(dictionary1, tableSize, firstId, type);
	}



	static void test()
	{
		int did = 11;
		int fid = 0;
		int w = 5;
		int h = 2;
		char buf[32];
		cv::Size sz(w, h);
		while (fid<50)
		{
			sprintf_s(buf, "table%d[%d-%d].png", did, fid, fid + sz.area() - 1);
			cv::Mat table = artable(sz, fid, did);
			cv::imshow(buf, table);
			cv::imwrite(buf, table);
			cv::waitKey();
			fid += sz.area();
		}
	}

	static void test2()
	{
		int did = 11;
		char buf[32];
		for (int i = 0; i<2; i++)
		{
			sprintf_s(buf, "arcode[%d-%d].png", did, i);
			cv::Mat code = artable(cv::Size(1, 1), i, did, 3);
			int edge = 5;
			cv::Mat m(edge * 2 + code.rows, edge * 2 + code.cols, code.type(), cv::Scalar::all(255));
			code.copyTo(m(cv::Rect(edge, edge, code.cols, code.rows)));
			cv::imwrite(buf, m);
		}
	}

	static void test3()
	{
		int did = 11;
		int fid = 0;
		int w = 5;
		int h = 10;
		char buf[32];
		cv::Size sz(w, h);
		while (fid<50)
		{
			sprintf_s(buf, "test3%d[%d-%d].png", did, fid, fid + sz.area() - 1);
			cv::Mat table = artable(cv::Size(2, 2), fid, did, 5);
			cv::imshow(buf, table);
			cv::imwrite(buf, table);
			cv::waitKey();
			fid += sz.area();
		}
	}

	static void test4()
	{
		int did = 11;
		int fid = 80;
		int w = 5;
		int h = 8;
		char buf[32];
		cv::Size sz(w, h);
		while (fid<120)
		{
			sprintf_s(buf, "test4%d[%d-%d].png", did, fid, fid + sz.area() - 1);
			cv::Mat table = artable(sz, fid, did, 5);
			cv::imshow(buf, table);
			cv::imwrite(buf, table);
			cv::waitKey();
			fid += sz.area();
		}
	}

	static void test5()
	{
		int fid = 120;
		int w = 5;
		int h = 8;
		char buf[32];
		char qrnum[5] = "0000";
		cv::Size sz(w, h);
		while (fid < 160)
		{
			sprintf_s(buf, "test5[%d-%d].png", fid, fid + sz.area() - 1);
			const QrCode qr = QrCode::encodeText(qrnum, QrCode::Ecc::HIGH);
			cv::Mat table = qrtable(sz, fid, 5);
			cv::imshow(buf, table);
			cv::imwrite(buf, table);
			cv::waitKey();
			fid += sz.area();
		}
	}

};

#endif // MARKERTABLE_HPP
